# Copyright (c) 2025 Linx Software, Inc.
#
# xlin-sbom-analysis tool is licensed under Mulan PSL v2.

# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
# http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import os
import re
import matplotlib.pyplot as plt
import numpy as np
from actions.data_helper import read_data_from_json
from actions import ASSIST_DIR


# 定义四大种类许可证的引入建议
LICENSE_TYPE_ADVICE = {
    "permissive": "宽松许可证、公共领域属于宽松型许可证。核心义务是保留原始版权和许可声明。建议在使用的代码文件中明确保留原始版权声明和许可文本，分发时须附带原始许可证副本，并注意兼容性约束。",
    "restricted": "有限著佐权许可证、受限免费许可证属于限制型许可证。建议通过动态链接或服务化隔离核心代码，明确区分派生作品与独立模块，对修改部分进行详细记录并开源，避免静态链接或代码混合。",
    "copyleft": "著佐权许可证属于强制型许可证。建议采用微服务或容器化架构隔离核心传染性组件，通过明确的API边界降低传染风险，动态链接替代静态链接，独立发布修改部分并公开源代码。",
    "special": "商业许可证、专有免费许可证、源码可见、贡献者许可协议、专利许可证、未声明许可证属于特殊型许可证。建议建立合规跟踪机制，明确贡献者版权归属，签署CLA或专利授权协议，避免未明确许可的代码集成。"
}

# 构建许可证类别详细信息列表
LICENSE_CATEGORY_DETAILS = [
    {
        "scancode_category": "CLA",
        "description": "贡献者许可协议 (Contributor License Agreement - CLA)：该许可证描述和定义了软件项目持续开发和增强过程中接受贡献的规则。CLA 可能规定最终的软件贡献本身将如何被授权许可。",
        "suggestion": LICENSE_TYPE_ADVICE["special"]
    },
    {
        "scancode_category": "Commercial",
        "description": "商业许可证 (Commercial)​：供应商和客户之间根据直接的商业许可协议提供的第三方专有软件。如无商业采购合同，贸然使用可能存在法律风险。",
        "suggestion": LICENSE_TYPE_ADVICE["special"]
    },
    {
        "scancode_category": "Copyleft",
        "description": "著佐权许可证 (Copyleft)​：采用'著佐权 (Copyleft)'许可的开源软件，授予公众不可撤销的复制和以相同或修改形式再分发作品的权限，但条件是所有此类再分发必须以便于进一步修改的形式提供作品，并使用相同的许可条款。著佐权许可要求与著佐权许可代码交互的代码也以相同方式获得许可（'传染性'）。",
        "suggestion": LICENSE_TYPE_ADVICE["copyleft"]
    },
    {
        "scancode_category": "Copyleft Limited",
        "description": "有限著佐权许可证 (Copyleft Limited)​：要求再分发源代码（包括修改）并为软件作者提供归属声明的许可证。再分发源代码（包括与此许可下的代码链接的专有代码）的义务，根据特定许可的规则受到限制。",
        "suggestion": LICENSE_TYPE_ADVICE["restricted"]
    },
    {
        "scancode_category": "Free Restricted",
        "description": "受限免费许可证 (Free Restricted)​：一种宽松式 (Permissive-style) 许可，但包含对软件使用（例如，软件不打算用于核电站）或软件再分发（例如，未经明确许可不得进行软件商业再分发）的限制。",
        "suggestion": LICENSE_TYPE_ADVICE["restricted"]
    },
    {
        "scancode_category": "Patent License",
        "description": "专利许可证 (Patent License)​：一种适用于专利而非特定软件的许可证。可以与适用于软件组件的其他软件许可证结合使用。",
        "suggestion": LICENSE_TYPE_ADVICE["special"]
    },
    {
        "scancode_category": "Permissive",
        "description": "宽松许可证 (Permissive)​：在'非著佐权 (non-copyleft)'许可下提供的开源软件。这类许可证通常要求对所包含的开源代码进行归属声明，并可能包含其他义务。",
        "suggestion": LICENSE_TYPE_ADVICE["permissive"]
    },
    {
        "scancode_category": "Proprietary Free",
        "description": "专有免费许可证 (Proprietary Free)​：可能不需要商业许可但可能有特定条款和条件的专有免费软件，产品团队有义务遵守这些条款和条件。其中一些条款和条件随代码提供、或在代码中包含、或出现在可点击下载的许可证中。",
        "suggestion": LICENSE_TYPE_ADVICE["special"]
    },
    {
        "scancode_category": "Public Domain",
        "description": "公共领域 (Public Domain)​：没有明确义务即可使用的开源软件，但根据组织政策，必须随代码保留其许可证声明。该匹配可能适用于软件、网站上的代码示例、已发布的公共领域规范或其他类型的出版物。",
        "suggestion": LICENSE_TYPE_ADVICE["permissive"]
    },
    {
        "scancode_category": "Source-available",
        "description": "源码可见 (Source-available)​：源码可见软件是通过源代码分发模式发布的软件，其安排允许查看源代码，某些情况下也允许修改，但不一定满足称为开源软件的标准。",
        "suggestion": LICENSE_TYPE_ADVICE["special"]
    },
    {
        "scancode_category": "Unstated License",
        "description": "未声明许可证 (Unstated License)​：具有版权声明但未明确声许可条款的第三方软件。常见示例包括来自出版物和网站的代码片段。",
        "suggestion": LICENSE_TYPE_ADVICE["special"]
    }
]

LICENSE_MAP = {}
licenses_file_path = os.path.join(ASSIST_DIR, 'spdx-licenses.json')
SPDX_LICENSES_LIST = read_data_from_json(licenses_file_path)

def _load_license_map():
    """
    加载许可证映射关系到全局LICENSE_MAP字典中

    Args:
        无参数

    Returns:
        dict: 包含许可证键值对的字典，键为SPDX许可证标识符，值为许可证类别
    """

    if not LICENSE_MAP:
        licenses = read_data_from_json(
            os.path.join(ASSIST_DIR, 'licenses.json'))
        for item in licenses:
            key = item.get("spdx_license_key")
            category = item.get("category", "Unknown")
            if key:
                LICENSE_MAP[key] = category
            for other_key in item.get("other_spdx_license_keys", []):
                LICENSE_MAP[other_key] = category
    return LICENSE_MAP


def _standardize_license_name(license_input: str) -> str:
    """
    将提供的许可名称标准化为 SPDX 标准名称。

    Args:
        license_input (str): 输入的许可名称，可能是 SPDX 标准名称或其变体。

    Returns:
        str: 标准化的 SPDX 许可名称。如果未找到匹配项，则返回原始输入。
    """

    # 定义分隔符的正则表达式模式
    delimiter_pattern = r'(?i)(\s+or\s+|\s+and\s+|[()&|])'

    # 使用正则表达式按分隔符进行分割
    segments = re.split(delimiter_pattern, license_input)

    # 创建一个用于存放 (alt_name, spdxName) 对的列表
    replacement_pairs = []
    for license_info in SPDX_LICENSES_LIST:
        spdx_name = license_info.get("spdx_name")
        if not spdx_name:
            continue
        for alt_name in license_info.get("alt_names", []):
            replacement_pairs.append((alt_name, spdx_name))

    # 按 alt_name 的长度从长到短排序，确保长的先替换
    replacement_pairs.sort(key=lambda x: len(x[0]), reverse=True)

    # 遍历每个片段，检查是否匹配任何许可证名
    for i, segment in enumerate(segments):
        for alt_name, spdx_name in replacement_pairs:
            # 忽略大小写的匹配
            if segment.strip().lower() == alt_name.lower():
                segments[i] = spdx_name
                break

    # 将片段重新组合为最终字符串，确保空格被保留
    return ''.join(segments)


def split_license(license_str):
    """
    拆分复合许可证字符串为单个许可证列表

    Args:
        license_str (str): 复合许可证字符串，可能包含OR、AND等逻辑操作符和括号

    Returns:
        list: 标准化后的单个许可证名称列表，去除了重复项
    """

    if not license_str:
        return []

    # 移除括号
    cleaned = re.sub(r'[()]', '', license_str)

    # 使用不区分大小写的正则表达式拆分OR或AND
    pattern = re.compile(r'\s+OR\s+|\s+AND\s+', re.IGNORECASE)
    parts = pattern.split(cleaned)

    results = []
    for part in parts:
        if part.strip():
            # 移除每个许可证中的with子句（不区分大小写）
            cleaned_part = re.sub(r'\s+with\s+.+$', '',
                                  part.strip(), flags=re.IGNORECASE)
            standard_license = _standardize_license_name(cleaned_part.strip())
            if standard_license not in results:
                results.append(standard_license)

    return results


def get_license_category(license_name):
    """
    获取许可证的类别

    Args:
        license_name (str): 许可证名称

    Returns:
        str: 许可证类别，如果未找到匹配项则返回"Unknown"
    """

    license_map = _load_license_map()
    return license_map.get(license_name, "Unknown")


def filter_licenses(license_list):
    """
    过滤掉许可证列表中包含'unknown'或'non-standard'等条目（不区分大小写）

    Args
        license_list (list): 许可证统计列表，每个元素是包含"name"和"count"的字典

    Returns:
        list: 过滤后的许可证列表
    """

    exclude_terms = {"unknown", "non-standard", "proprietary-license"}
    return [
        license for license in license_list
        if not any(term in license["name"].lower() for term in exclude_terms)
    ]


def count_licenses(licenses):
    """
    统计许可证出现次数并转换为摘要格式

    Args:
        licenses (list): 许可证列表，包含所有检测到的许可证名称

    Returns:
        list: 许可证摘要列表，每个元素是包含"name"和"count"键的字典，
              分别表示许可证名称和出现次数
    """

    license_counter = {}

    for license in licenses:
        license_counter[license] = license_counter.get(license, 0) + 1

    # 转换为generate_pie_chart所需的格式
    license_summary = [
        {"name": license, "count": count}
        for license, count in license_counter.items()
    ]

    return license_summary


def licenses_visualization(license_summary, output_file):
    """
    生成许可证分布的饼图可视化图表并保存到文件

    Args:
        license_summary (list): 许可证摘要列表，每个元素是包含"name"和"count"键的字典，
                               分别表示许可证名称和出现次数
        output_file (str): 输出图片文件的路径，包括文件名和扩展名

    Returns:
        None: 该函数不返回任何值，直接将生成的图表保存到指定路径
    """

    # 按计数值排序（从大到小）
    sorted_licenses = sorted(
        license_summary, key=lambda x: x['count'], reverse=True)

    # 计算总计数
    total_count = sum(item['count'] for item in sorted_licenses)

    # 处理总计数为0的情况
    if total_count == 0:
        return

    main_data = []
    other_licenses = []
    other_count = 0
    cumulative_percentage = 0.0
    cumulative_threshold = 90.0

    # 分离前90%的许可证和剩余部分
    for item in sorted_licenses:
        name = item['name']
        count = item['count']
        percentage = (count / total_count) * 100

        if cumulative_percentage < cumulative_threshold or not main_data:
            main_data.append((name, count))
            cumulative_percentage += percentage
        else:
            other_licenses.append(name)
            other_count += count

    # 如果有剩余许可证则添加"Other"类别
    if other_count > 0:
        main_data.append(("Other", other_count))

    # 提取数值和构建图例标签
    sizes = [count for _, count in main_data]
    total = sum(sizes)

    # 创建饼图
    fig, ax = plt.subplots(figsize=(12, 9))
    colors = plt.cm.tab20c(np.linspace(0, 1, len(sizes)))
    wedges, texts = ax.pie(
        sizes,
        colors=colors,
        startangle=90,
        wedgeprops=dict(width=0.4, edgecolor='w')
    )
    centre_circle = plt.Circle(
        (0, 0), 0.3, facecolor='white', edgecolor='lightgray')
    fig.gca().add_artist(centre_circle)
    ax.text(0, 0, f"Total\n{total}", ha='center', va='center', fontsize=12)

    # 构建完整图例标签（名称 + 计数 + 百分比）
    legend_labels = []
    for i, (name, count) in enumerate(main_data):
        percent = 100. * count / total

        # 为"Other"类别添加特殊处理
        if name == "Other":
            # 限制显示的许可证数量以避免图例过长
            max_display = 10
            displayed_licenses = other_licenses[:max_display]
            hidden_count = len(other_licenses) - max_display

            other_details = "\n".join(
                [f"· {license}" for license in displayed_licenses])
            if hidden_count > 0:
                other_details += f"\n· ...and {hidden_count} more"
            label = f"Other ({other_count}, {percent:.1f}%):\n{other_details}"
        else:
            label = f"{name} ({count}, {percent:.1f}%)"
        legend_labels.append(label)

    # 将图例放在饼图右侧
    plt.legend(
        wedges,
        legend_labels,
        title="Licenses Distribution",
        loc="center left",
        bbox_to_anchor=(1.05, 0.5),
        fontsize=9,
        title_fontsize=11
    )

    # 添加标题
    plt.title('Licenses Distribution', fontsize=16, pad=20)

    # 确保饼图是圆形
    ax.axis('equal')

    # 调整布局并保存
    plt.subplots_adjust(right=0.65)  # 为右侧图例留出空间
    plt.savefig(output_file, dpi=200, bbox_inches='tight')
    plt.close(fig)